package com.smsc.headend.connector.utils;

import gurux.dlms.*;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Cosem46ByteUtil {
    static final byte HDLC_FRAME_START_END = 0x7E;

    /**
     * 获取FrameType位置 如 7E A0 19 03 03 32 EC C8 E6 E6 00 C0 01 41 00 01 00 00 2A 00 00 FF 02 00 09 12 7E(32为FrameType Position)
     * @param client
     * @return
     */
    public static int getFrameTypePosition(GXDLMSClient client) {
        int serverAddr = client.getServerAddress();
        int clientAddr = client.getClientAddress();
        return 3 + GxAddrUtil.getHDLCServerAddSize(serverAddr) + GxAddrUtil.getHDLCServerAddSize(clientAddr) + 1;
    }

    /**
     * 获取FrameType位置 如 7E A0 22 03 03 52 0D 27 E6 E7 00 C4 01 42 00 09 10 57 53 45 30 30 30 30 30 30 30 30 30 30 30 31 36 E1 4C 7E(42为FrameType Position)
     * @param client
     * @return
     */
    public static int getInvokeIdPosition(GXDLMSClient client) {
        int serverAddr = client.getServerAddress();
        int clientAddr = client.getClientAddress();
        return 3 + GxAddrUtil.getHDLCServerAddSize(serverAddr) + GxAddrUtil.getHDLCServerAddSize(clientAddr) + 9;
    }

    /**
     * 获取FrameType位置 如 7E A0 22 03 03 52 0D 27 E6 E7 00 C4 01 42 00 09 10 57 53 45 30 30 30 30 30 30 30 30 30 30 30 31 36 E1 4C 7E(42为FrameType Position)
     * @param client
     * @return
     */
    public static int getInvokeId(GXDLMSClient client, byte[] packetByte) {
        int framePos = getInvokeIdPosition(client);
        if (packetByte[0] != HDLC_FRAME_START_END) {
            // 非46 HDLC的报文暂时这样处理
            return -1;
        }
        return packetByte[framePos -1];
    }

    public static short getFrameType(GXDLMSClient client, byte[] packetByte) {
        int framePos = getFrameTypePosition(client);
        if (packetByte[0] != HDLC_FRAME_START_END) {
            // 非46 HDLC的报文暂时这样处理
            return -1;
        }
        return packetByte[framePos -1];
    }

    /**
     * 替换头部的crc 如 7E A0 19 03 03 32 EC C8 E6 E6 00 C0 01 41 00 01 00 00 2A 00 00 FF 02 00 09 12 7E (EC C8 为header crc)
     * @param packetByte
     * @param posLength
     * @return
     */
    public static byte[] replaceHeaderCrcPacket(byte[] packetByte, int posLength) {
        // 0x7E开头的为46 HDLC的报文
        if (packetByte[0] != HDLC_FRAME_START_END) {
            return packetByte;
        }
        byte[] result = new byte[posLength];
        System.arraycopy(packetByte, 0, result, 0, result.length);

        GXByteBuffer bb = new GXByteBuffer(result);
        int crc = GxFcsUtil.countFCS16(bb.getData(), 1, bb.size()-1);

        packetByte[posLength] = (byte) ((crc >> 8) & 0xFF);
        packetByte[posLength + 1] = (byte) (crc & 0xFF);

        return packetByte;
    }

    /**
     * 替换头部的crc 如 7E A0 19 03 03 32 EC C8 E6 E6 00 C0 01 41 00 01 00 00 2A 00 00 FF 02 00 09 12 7E (09 12 为data crc)
     * @param packetByte
     * @return
     */
    public static byte[] replaceDataCrcPacket(byte[] packetByte) {
        // 0x7E开头的为46 HDLC的报文
        if (packetByte[0] != HDLC_FRAME_START_END) {
            return packetByte;
        }
        // 倒数第三位和第二位 为crc,结束位为0X7E结束标志位 所以一共3位减去3再计算
        byte[] result = new byte[packetByte.length - 3];
        System.arraycopy(packetByte, 0, result, 0, result.length);

        GXByteBuffer bb = new GXByteBuffer(result);

        int crc = GxFcsUtil.countFCS16(bb.getData(), 1, bb.size()-1);
        bb.setUInt16(crc);
        bb.setUInt8(HDLC_FRAME_START_END);

        System.arraycopy(bb.getData(), 0, packetByte, 0, packetByte.length);

        return packetByte;
    }

    /**
     * 替换frameType
     * @param packetByte
     * @param framePosition
     * @param frameId
     * @return
     */
    public static byte[] replaceFrameTypePacket(byte[] packetByte, int framePosition, short frameId) {
        // 0x7E开头的为46 HDLC的报文
        if (packetByte[0] != HDLC_FRAME_START_END) {
            return packetByte;
        }

        packetByte[framePosition-1] = (byte) increaseSendSequence(frameId);

        return packetByte;
    }

    /**
     * 参考 GXDLMSSettings 里面方法
     * @param value
     * @return
     */
    public static short increaseSendSequence(final short value) {
        return (short) ((value & 0xF0 | (value + 0x2) & 0xE) & 0xFF);
    }


    /**
     * 根据发的帧反推上次收到的帧序号
     * @param value
     * @return
     */
    public static short decreaseSendSequence(final short value) {
        return (short) ((value & 0xF0 | (value - 0x2) & 0xE) & 0xFF);
    }

    /**
     * Increase receiver sequence.
     *
     * @param value Frame value.
     * @return Increased receiver frame sequence.
     */
    public static byte increaseReceiverSequence(final short value) {
        return (byte) ((value & 0xFF) + 0x20 | 0x10 | value & 0xE);
    }

    /**
     * 根据发的帧反推上次收到的帧序号
     * @param value
     * @return
     */
    public static byte decreaseReceiverSequence(final short value) {
        return (byte) ((value & 0xFF) - 0x20 | 0x10 | value & 0xE);
    }

    /**
     * UTE serverAddress,clientAddress目前都为1 组的报文为 7E A0 19 03 03 10 FC CA E6 E6 00 C0 01 C1 00 08 00 00 01 00 00 FF 02 00 60 1A 7E 取出 E6 E6 00 C0 01 C1 00 08 00 00 01 00 00 FF 02 00 为data数据
     * @param startPosition
     * @param packetByte
     * @return
     */
    public static byte[] getHdlcData(int startPosition, byte[] packetByte) {
        // 0x7E开头的为46 HDLC的报文
        if (packetByte[0] != HDLC_FRAME_START_END) {
            return packetByte;
        }
        // 倒数第三位和第二位 为crc,结束位为0X7E结束标志位 所以一共3位再减去3再计算
        int dataLength = packetByte.length -startPosition - 3;
        byte[] result = new byte[dataLength];
        System.arraycopy(packetByte, startPosition, result, 0, result.length);
        return result;
    }

    public static byte[] getHdlcFrame(final GXDLMSClient client, final int frame, final GXByteBuffer data) {
        GXByteBuffer bb = new GXByteBuffer();
        int frameSize, len = 0;
        byte[] primaryAddress, secondaryAddress;
        // Add BOP
        bb.setUInt8(HDLC_FRAME_START_END);
        frameSize = client.getHdlcSettings().getMaxInfoTX();
        primaryAddress = getAddressBytes(client.getServerAddress(), client.getServerAddressSize());
        secondaryAddress = getAddressBytes(client.getClientAddress(), 0);
        len = primaryAddress.length;
        // Remove BOP, type, len, primaryAddress, secondaryAddress, frame,
        // header CRC, data CRC and EOP from data length.
        if (client.getHdlcSettings().isUseFrameSize()) {
            frameSize -= (10 + len);
        } else {
            if (data != null && data.position() == 0) {
                frameSize -= 3;
            }
        }

        // If no data
        if (data == null || data.size() == 0) {
            len = 0;
            bb.setUInt8(0xA0);
        } else if (data.size() - data.position() <= frameSize) {
            len = data.size() - data.position();
            // Is last packet.
            bb.setUInt8(0xA0 | (((7 + primaryAddress.length + secondaryAddress.length + len) >> 8) & 0x7));
        } else {
            len = frameSize;
            // More data to left.
            bb.setUInt8(0xA8 | ((7 + primaryAddress.length + secondaryAddress.length + len >> 8) & 0x7));
        }
        // Frame len.
        if (len == 0) {
            bb.setUInt8((byte) (5 + primaryAddress.length + secondaryAddress.length + len));
        } else {
            bb.setUInt8((byte) (7 + primaryAddress.length + secondaryAddress.length + len));
        }
        // Add primary address.
        bb.set(primaryAddress);
        // Add secondary address.
        bb.set(secondaryAddress);

        // Add frame ID.
        bb.setUInt8(frame);
        // Add header CRC.
        int crc = GxFcsUtil.countFCS16(bb.getData(), 1, bb.size() - 1);
        bb.setUInt16(crc);
        if (len != 0) {
            // Add data.
            bb.set(data, len);
            // Add data CRC.
            crc = GxFcsUtil.countFCS16(bb.getData(), 1, bb.size() - 1);
            bb.setUInt16(crc);
        }
        // Add EOP
        bb.setUInt8(HDLC_FRAME_START_END);
        return bb.array();
    }

    private static byte[] getAddressBytes(final int value, final int size) {
        Object tmp = getAddress(value, size);
        GXByteBuffer bb = new GXByteBuffer();
        if (tmp instanceof Byte) {
            bb.setUInt8(((Byte) tmp).byteValue());
        } else if (tmp instanceof Short) {
            bb.setUInt16(((Short) tmp).intValue());
        } else if (tmp instanceof Integer) {
            bb.setUInt32(((Integer) tmp).intValue());
        } else if (tmp instanceof GXUInt8) {
            bb.setUInt8(((GXUInt8) tmp).byteValue());
        } else if (tmp instanceof GXUInt16) {
            bb.setUInt16(((GXUInt16) tmp).intValue());
        } else if (tmp instanceof GXUInt32) {
            bb.setUInt32(((GXUInt32) tmp).intValue());
        } else {
            throw new IllegalArgumentException("Invalid address type.");
        }
        return bb.array();
    }

    static Object getAddress(final long value, final int size) {
        if (size < 2 && value < 0x80) {
            return (byte) (value << 1 | 1);
        }
        if (size < 4 && value < 0x4000) {
            return (short) ((value & 0x3F80) << 2 | (value & 0x7F) << 1 | 1);
        }
        if (value < 0x10000000) {
            return (int) ((value & 0xFE00000) << 4 | (value & 0x1FC000) << 3 | (value & 0x3F80) << 2
                    | (value & 0x7F) << 1 | 1);
        }
        throw new IllegalArgumentException("Invalid address.");
    }

    public static byte[] getHdlcReleasePacket(final GXDLMSClient client) {
        GXByteBuffer bb = new GXByteBuffer();
        int len = 0;
        byte[] primaryAddress, secondaryAddress;
        // Add BOP
        bb.setUInt8(HDLC_FRAME_START_END);
        primaryAddress = getAddressBytes(client.getServerAddress(), client.getServerAddressSize());
        secondaryAddress = getAddressBytes(client.getClientAddress(), 0);
        bb.setUInt8(0xA0);
        // Frame len.
        if (len == 0) {
            bb.setUInt8((byte) (5 + primaryAddress.length + secondaryAddress.length + len));
        } else {
            bb.setUInt8((byte) (7 + primaryAddress.length + secondaryAddress.length + len));
        }
        // Add primary address.
        bb.set(primaryAddress);
        // Add secondary address.
        bb.set(secondaryAddress);
        // Add frame ID.
        bb.setUInt8(0x53);
        int crc = GxFcsUtil.countFCS16(bb.getData(), 1, bb.size() - 1);
        bb.setUInt16(crc);
        // Add EOP
        bb.setUInt8(HDLC_FRAME_START_END);
        return bb.array();
    }

}
